/**
 * 版权所有(C)，上海海鼎信息工程股份有限公司，2009，所有权利保留。
 * 
 * 项目名：	rumba-widget2
 * 文件名：	RPopupCalendar.java
 * 模块说明：	
 * 修改历史：
 * Feb 11, 2009 - lxm - 创建。
 */
package com.hd123.rumba.gwt.widget2.client.form;

import java.util.Date;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.HTMLTable.Cell;
import com.hd123.rumba.gwt.util.client.RDateUtil;

/**
 * 
 * 弹出式日历，用于选择一个日期。设计用于{@link RDateBox}。
 * <p>
 * 
 * <b>CSS Style Rules</b> <ol type=disc>
 * <li>.rb-PopupCalendar { 整个控件 }</li>
 * <li>.rb-PopupCalendar .month-line { 月份行 }</li>
 * <li>.rb-PopupCalendar .month-title { 月份行中的显示年月的单元格 }</li>
 * <li>.rb-PopupCalendar .week-line { 星期行 }</li>
 * <li>.rb-PopupCalendar .day-line { 日期行 }</li>
 * <li>.rb-PopupCalendar .day-line .in-month { 当月日期单元格 }</li>
 * <li>.rb-PopupCalendar .day-line .out-month { 非当月日期单元格 }</li>
 * <li>.rb-PopupCalendar .day-line .week-end { 周末日期单元格 }</li>
 * <li>.rb-PopupCalendar .day-line .selected { 选中日期单元格 }</li>
 * <li>.rb-PopupCalendar .day-line .today { 今天日期单元格 }</li> </ol>
 * 
 * @author lxm
 * 
 */
public class RPopupCalendar extends PopupPanel {

  private static final String STYLENAME_DEFAULT = "rb-PopupCalendar";

  public RPopupCalendar() {
    super(true);
    drawSelf();
    Date today = new Date();
    setSelectedDate(today);
  }

  private Date selectedDate;
  private Date displayedMonth;
  private FlexTable table;
  private RPopupCalendarListener calendarListener;

  private static final int ROW_MONTH_LINE = 0;
  private static final int COL_PREVIOUS_YEAR = 0;
  private static final int COL_PREVIOUS_MONTH = 1;
  private static final int COL_TITLE = 2;
  private static final int COL_NEXT_MONTH = 3;
  private static final int COL_NEXT_YEAR = 4;
  private static final int ROW_WEEK_LINE = 1;
  private static final int ROW_FIRST_DAY_LINE = 2;
  private static final DateTimeFormat FMT_TITLE = DateTimeFormat.getFormat("yyyy年M月");

  private static final String[] DAYOFWEEK_CN = new String[] {
      "日", "一", "二", "三", "四", "五", "六" };

  /** 当前选择日期，精确到日。默认为今天。 */
  public Date getSelectedDate() {
    return selectedDate;
  }

  /** 当前选择日期，精确到日。默认为今天。 */
  public void setSelectedDate(Date selectedDate) {
    this.selectedDate = RDateUtil.datePart(selectedDate);
    displayedMonth = RDateUtil.getFirstDayOfMonth(this.selectedDate);
    refresh();
  }

  /**
   * 设置日历控件侦听器对象。
   * 
   * @param listener
   */
  public void setCalendarListener(RPopupCalendarListener listener) {
    calendarListener = listener;
  }

  public void show() {
    refresh();
    super.show();
  }

  public void setPopupPositionAndShow(PositionCallback callback) {
    refresh();
    super.setPopupPositionAndShow(callback);
  }

  private void drawSelf() {
    table = new FlexTable();
    table.setCellPadding(0);
    table.setCellSpacing(0);

    drawMonthLine();
    drawWeekLine();

    table.addClickHandler(new TableHandler());
    setWidget(table);
    setStyleName(STYLENAME_DEFAULT);
  }

  private void drawMonthLine() {
    table.getRowFormatter().setStyleName(ROW_MONTH_LINE, "month-line");

    table.setHTML(ROW_MONTH_LINE, COL_PREVIOUS_YEAR,
        "<span style='font-family: Webdings;'>7</span>");

    table.setHTML(ROW_MONTH_LINE, COL_PREVIOUS_MONTH,
        "<span style='font-family: Webdings;'>3</span>");

    table.getFlexCellFormatter().setColSpan(ROW_MONTH_LINE, COL_TITLE, 3);
    table.getCellFormatter().setStyleName(ROW_MONTH_LINE, COL_TITLE, "month-title");

    table.setHTML(ROW_MONTH_LINE, COL_NEXT_MONTH, "<span style='font-family: Webdings;'>4</span>");

    table.setHTML(ROW_MONTH_LINE, COL_NEXT_YEAR, "<span style='font-family: Webdings;'>8</span>");
  }

  private void drawWeekLine() {
    table.getRowFormatter().setStyleName(ROW_WEEK_LINE, "week-line");
    for (int i = 0, n = DAYOFWEEK_CN.length; i < n; i++) {
      table.setText(ROW_WEEK_LINE, i, DAYOFWEEK_CN[i]);
    }
  }

  /**
   * 根据{@link #displayedMonth}刷新显示月份。
   */
  private void refresh() {
    refreshTitle();
    refreshDaysGrid();
  }

  private void refreshTitle() {
    String title = FMT_TITLE.format(displayedMonth);
    table.setText(ROW_MONTH_LINE, COL_TITLE, title);
  }

  private void refreshDaysGrid() {
    FlexTable.RowFormatter rf = table.getRowFormatter();
    FlexTable.CellFormatter cf = table.getCellFormatter();
    Date today = new Date();

    Date date = getDaysGridOrigin(displayedMonth);
    for (int row = ROW_FIRST_DAY_LINE; row < ROW_FIRST_DAY_LINE + 7; row++) {
      rf.setStyleName(row, "day-line");

      for (int col = 0; col < 7; col++) {
        table.setText(row, col, String.valueOf(RDateUtil.get(date, RDateUtil.DAY_OF_MONTH)));

        // 计算StyleName
        StringBuffer sb = new StringBuffer();
        if (RDateUtil.equals(date, displayedMonth, RDateUtil.MONTH))
          sb.append("in-month");
        else
          sb.append("out-month");
        int dayOfWeek = RDateUtil.get(date, RDateUtil.DAY_OF_WEEK);
        if (dayOfWeek == RDateUtil.SUNDAY || dayOfWeek == RDateUtil.SATURDAY)
          sb.append(" week-end");
        if (areEquals(date, selectedDate))
          sb.append(" selected");
        if (areEquals(date, today))
          sb.append(" today");
        cf.setStyleName(row, col, sb.toString());

        date = RDateUtil.addDay(date, 1);
      }
    }
  }

  private void changeMonth(int offsetMonths) {
    displayedMonth = RDateUtil.add(displayedMonth, RDateUtil.MONTH, offsetMonths);
    refresh();
  }

  private void doClick(int row, int col) {
    Date baseDate = getDaysGridOrigin(displayedMonth);
    int days = 7 * (row - ROW_FIRST_DAY_LINE) + col;
    selectedDate = RDateUtil.addDay(baseDate, days);

    hide();

    if (calendarListener != null)
      calendarListener.onSelected(this, selectedDate);
  }

  /**
   * 表格事件处理
   * 
   * @author LiQi
   * 
   */
  private class TableHandler implements ClickHandler {
    public void onClick(ClickEvent event) {
      Cell cell = table.getCellForEvent(event);
      if (cell.getRowIndex() == ROW_MONTH_LINE) {
        switch (cell.getCellIndex()) {
        case COL_PREVIOUS_YEAR:
          changeMonth(-12);
          break;
        case COL_PREVIOUS_MONTH:
          changeMonth(-1);
          break;
        case COL_NEXT_MONTH:
          changeMonth(1);
          break;
        case COL_NEXT_YEAR:
          changeMonth(12);
          break;
        }
      } else if (cell.getRowIndex() >= ROW_FIRST_DAY_LINE) {
        doClick(cell.getRowIndex(), cell.getCellIndex());
      }
    }
  }

  private static boolean areEquals(Date date1, Date date2) {
    return RDateUtil.equals(date1, date2, RDateUtil.DAY_OF_MONTH)
        && RDateUtil.equals(date1, date2, RDateUtil.MONTH)
        && RDateUtil.equals(date1, date2, RDateUtil.YEAR);
  }

  /**
   * 取得显示的第一个日期。
   * 
   * @param displayedMonth
   *          指定需要显示的月份。
   * @return
   */
  private static Date getDaysGridOrigin(Date displayedMonth) {
    Date monthFirstDay = RDateUtil.getFirstDayOfMonth(displayedMonth);
    int indice = RDateUtil.get(monthFirstDay, RDateUtil.DAY_OF_WEEK);
    Date origineTableau;
    origineTableau = RDateUtil.getDayOfWeek(monthFirstDay, RDateUtil.SUNDAY);
    if (indice <= 4)
      origineTableau = RDateUtil.addDay(origineTableau, -7);
    return origineTableau;
  }
}
